<?

	class Core_UpdateManager
	{
		const allowed_license_change_num = 3;

		protected static $instance = null;
		
		protected function __construct()
		{
		}
		
		public static function create()
		{
			if (!self::$instance)
				self::$instance = new Core_UpdateManager();

			return self::$instance;
		}

		protected function request_server_data($url, $fields = array(), $force = false)
		{
			if (!$force && Phpr::$config->get('FREEZE_UPDATES'))
				throw new Exception("We are sorry, updates were blocked by the system administrator.");
			
			$uc_url = Phpr::$config->get('UPDATE_CENTER');
			if (!strlen($uc_url))
				throw new Exception('Limewheel Creative Inc. server URL is not specified in the configuration file.');
				
			Backend::$events->fireEvent('core:onBeforeSoftwareUpdateRequest');

			$result = null;
			try
			{
				$poststring = array();

				foreach($fields as $key=>$val)
					$poststring[] = urlencode($key)."=".urlencode($val); 

				$poststring = implode('&', $poststring);
				
				$ch = curl_init(); 
				curl_setopt($ch, CURLOPT_URL, 'http://'.$uc_url.'/'.$url);
				curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
				curl_setopt($ch, CURLOPT_TIMEOUT, 3600);
				curl_setopt($ch, CURLOPT_POSTFIELDS, $poststring);
				$result = curl_exec($ch);
				
				if (curl_errno($ch))
					throw new Phpr_ApplicationException( "Error connecting the update server." );
				else
					curl_close($ch);

			} catch (Exception $ex) {}

			if (!$result || !strlen($result))
				throw new Exception("Error connecting to the Limewheel Creative Inc. server.");

			$result_data = false;
			try
			{
				$result_data = @unserialize($result);
			} catch (Exception $ex) {
				throw new Exception("Invalid response from the Limewheel Creative Inc. server.");
			}

			if ($result_data === false)
				throw new Exception("Invalid response from the Limewheel Creative Inc. server.");

			if ($result_data['error'])
				throw new Exception($result_data['error']);
				
			Backend::$events->fireEvent('core:onAfterSoftwareUpdateRequest', $result_data);

			return $result_data;
		}
		
		protected function get_module_versions()
		{
			$result = array();
			
			$modules = Core_ModuleManager::listModules();
			foreach ($modules as $module)
			{
				$module_info = $module->getModuleInfo();
				$module_id = mb_strtolower($module->getId());
				$build = $module_info->getVersion();
				
				$result[$module_id] = $build;
			}
			
			return $result;
		}
		
		protected function get_hash()
		{
			$hash = Db_ModuleParameters::get('core', 'hash');
			if (!$hash)
				throw new Phpr_ApplicationException('License information not found');
			
			$framework = Phpr_SecurityFramework::create();
			return $framework->decrypt(base64_decode($hash));
		}
		
		public function request_update_list($hash = null, $force = false)
		{
			if (!$force && Phpr::$config->get('FREEZE_UPDATES'))
				throw new Exception("We are sorry, updates were blocked by the system administrator.");

			$hash = $hash ? $hash : $this->get_hash();
			$url = base64_encode(root_url('/', true, 'http'));

			$fields = array(
				'versions'=>serialize($this->get_module_versions()),
				'url'=>$url,
				'disabled'=>serialize(Phpr::$config->get('DISABLE_MODULES', array()))
			);

			$response = $this->request_server_data('get_update_list/'.$hash, $fields, $force);
			if (!isset($response['data']))
				new Phpr_ApplicationException('Invalid server response.');
				
			if (!count($response['data']))
				Db_ModuleParameters::set('backend', 'ls_updates_available', 0);

			return $response;
		}
		
		public function update_application($cli_mode = false, $force = false)
		{
			@set_time_limit(3600);
			
			if (Phpr::$config->get('FREEZE_UPDATES'))
				throw new Exception("We are sorry, updates were blocked by the system administrator.");

			if (!$force)
			{
				$update_data = $this->request_update_list();
				$update_list = $update_data['data'];
				$fields = array(
					'modules'=>serialize(array_keys($update_list)),
					'disabled'=>serialize(Phpr::$config->get('DISABLE_MODULES', array()))
				);
			} else
			{
				$versions = $this->get_module_versions();
				$fields = array(
					'modules'=>serialize(array_keys($versions)),
					'disabled'=>serialize(Phpr::$config->get('DISABLE_MODULES', array()))
				);
			}
			
			if (!is_writable(PATH_APP) || !is_writable(PATH_APP.'/modules') || !is_writable(PATH_APP.'/phproad'))
				throw new Exception('The LemonStand directory ('.PATH_APP.') is not writable for PHP.');

			$hash = $this->get_hash();
			$result = $this->request_server_data('get_update_hashes/'.$hash, $fields);
			$file_hashes = $result['data'];

			if (!is_array($file_hashes))
				throw new Exception("Invalid server response");

			$tmp_path = PATH_APP.'/temp';
			if (!is_writable($tmp_path))
				throw new Exception("Cannot create temporary file. Path is not writable: $tmp_path");

			$files = array();
			try
			{
				foreach ($file_hashes as $code=>$file_hash)
				{
					$tmp_file = $tmp_path.'/'.$code.'.arc';
					$result = $this->request_server_data('get_update_file/'.$hash.'/'.$code);

					$tmp_save_result = false;
					try
					{
						$tmp_save_result = @file_put_contents($tmp_file, $result['data']);
					} catch (Exception $ex)
					{
						throw new Exception("Error creating temporary file in ".$tmp_path);
					}

					$files[] = $tmp_file;

					if (!$tmp_save_result)
						throw new Exception("Error creating temporary file in ".$tmp_path);

					$downloaded_hash = md5_file($tmp_file);
					if ($downloaded_hash != $file_hash)
						throw new Exception("Downloaded archive is corrupted. Please try again.");
				}

				Backend::$events->fireEvent('core:onBeforeSoftwareUpdate');
				
				foreach ($files as $file)
					Core_ZipHelper::unzip(PATH_APP, $file, $cli_mode);

				$this->update_cleanup($files);
			
				Db_UpdateManager::update();

				Db_ModuleParameters::set('backend', 'ls_updates_available', 0);

				Backend::$events->fireEvent('core:onAfterSoftwareUpdate');
			} catch (Exception $ex)
			{
				$this->update_cleanup($files);

				throw $ex;
			}
		}
		
		protected function update_cleanup($files)
		{
			foreach ($files as $file)
			{
				if (file_exists($file))
					@unlink($file);
			}
		}
		
		public function get_updates_flag()
		{
			if (!Phpr::$config->get('AUTO_CHECK_UPDATES', true))
				return false;

			if (Phpr::$config->get('FREEZE_UPDATES'))
				return false;
			
			if (Db_ModuleParameters::get('backend', 'ls_updates_available', false))
				return true;

			try
			{
				$last_check = Db_ModuleParameters::get('backend', 'ls_last_update_check', null);
				if (strlen($last_check))
				{
					try
					{
						$last_check_time = new Phpr_DateTime($last_check);

						$check_interval = Phpr::$config->get('UPDATE_CHECK_INTERVAL', 24);
						if (Phpr_DateTime::now()->substractDateTime($last_check_time)->getHoursTotal() > $check_interval)
							$last_check = false;
					} catch (Exception $ex) {}
				}

				if (!$last_check)
				{
					try
					{
						$update_data = Core_UpdateManager::create()->request_update_list();
						$updates = $update_data['data'];
						
						Db_ModuleParameters::set('backend', 'ls_updates_available', count($updates));
					} catch (Exception $ex) {}

					$last_check = Db_ModuleParameters::set('backend', 'ls_last_update_check', 
						Phpr_DateTime::now()->format(Phpr_DateTime::universalDateTimeFormat)
					);
				}
			} catch (Exception $ex) {}
		}
		
		public function cli_update()
		{
			Core_Cli::print_line();
			Core_Cli::print_line('LEMONSTAND UPDATE TOOL');
			Core_Cli::print_line();
			
			if (Phpr::$config->get('FREEZE_UPDATES'))
			{
				Core_Cli::print_error('We are sorry, updates were blocked by the system administrator.');
				exit(1);
			}
			
			/*
			 * Check the writing permissions
			 */
			
			if (!is_writable(PATH_APP) || !is_writable(PATH_APP.'/modules') || !is_writable(PATH_APP.'/phproad'))
			{
				Core_Cli::print_error('The LemonStand directory ('.PATH_APP.') is not writable for PHP.');
				exit(1);
			}
			
			Core_Cli::print_line('Requesting updates...');
			
			try
			{
				$update_data = $this->request_update_list();
				$update_list = $update_data['data'];
				
				if (!count($update_list))
				{
					Core_Cli::print_line('No updates found.');
					exit(0);
				}
				
				Core_Cli::print_line('The following updates were found:');
				Core_Cli::print_line();

				foreach ($update_list as $module_code=>$update_data)
				{
					Core_Cli::print_line('MODULE: '.$update_data->name);
					foreach ($update_data->updates as $version=>$description)
					{
						Core_Cli::print_line('Version: '.$version);
						Core_Cli::print_line('Description: '.$description);
					}
					Core_Cli::print_line();
				}

				if (!Core_Cli::read_bool_option('Do you want to install the updates? (Y/N): '))
					exit(1);
					
				Core_Cli::print_line('Updating LemonStand...');
				$this->update_application(true);
			} catch (Exception $ex)
			{
				Core_Cli::print_error($ex->getMessage());
				exit(1);
			}
			
			Core_Cli::print_line('LemonStand has been successfully updated.');
			exit(0);
		}
		
		public function get_license_change_num()
		{
			return Db_ModuleParameters::get('core', 'license_change_num', 0);
		}
		
		public function set_license_info($data)
		{
			$change_num = $this->get_license_change_num();
			$max_change_num = Core_UpdateManager::allowed_license_change_num;
			
			if ($change_num >= self::allowed_license_change_num)
				throw new Phpr_ApplicationException('We are sorry, you cannot change the license details of this LemonStand installation more than '.$max_change_num.' times');
			
			$validation = new Phpr_Validation();
			$validation->add('serial_number', 'Serial number')->fn('trim')->required('Please enter the serial number');
			$validation->add('holder_name', 'Holder name')->fn('trim')->required('Please enter the holder name');
			$validation->add('license_key', 'License key')->fn('trim')->required('Please enter the license key');
			
			if (!$validation->validate($data))
				$validation->throwException();
				
			$serial_number = $validation->fieldValues['serial_number'];
			$holder_name = $validation->fieldValues['holder_name'];
			$new_license_key = $validation->fieldValues['license_key'];

			$framework = Phpr_SecurityFramework::create();
			$new_hash = base64_encode($framework->encrypt(md5($serial_number.$holder_name)));

			$this->request_update_list(md5($serial_number.$holder_name), true);

			Db_ModuleParameters::set('core', 'hash', $new_hash);
			Db_ModuleParameters::set('core', 'license_key', $new_license_key);
			Db_ModuleParameters::set('core', 'license_change_num', $change_num+1);
		}
	}

?>